﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Silverlight.Testing;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using XcoAppSpaces.Core;
using System.Threading;
using Microsoft.Ccr.Core;
using testServer.XcoAppSpaces.SL;
using System.Diagnostics;
using System.Linq;
using XcoAppSpaces.Transport.Sockets;
using System.Text;
using XcoAppSpaces.Contracts.Exceptions;
using XcoAppSpaces.Core.CcrWiring;
using XcoAppSpaces.Contracts.Messaging;
using XcoAppSpaces.Contracts.Communication;
using System.Reflection;
using XcoAppSpaces.Core.Communication;
using XcoAppSpaces.Contracts.CcrWiring;
using System.Collections.Generic;

namespace test.XcoAppSpaces.SL
{
	/// <summary>
	/// These tests can only be successful when the server (testServer.XcoAppSpaces.SL) is running at the same time!!
	/// </summary>
	[TestClass]
	public class RemoteIntegrationTests
	{
		[TestMethod]
		public void SingleRequestResponse()
		{
			using (var space = new XcoAppSpace("tcp.port=0"))
			{
				var worker = space.Connect<SpaceMessage>("localhost:4505");
				var progressPort = new Port<double>();
				var responsePort = new Port<double>();
				worker.Post(new SpaceMessage { Num1 = 1, Num2 = 2, ProgressPort = progressPort, ResponsePort = responsePort });

				var response = responsePort.TestSync(3000);
				Assert.AreEqual(3, (int)response);
				Assert.AreEqual(20, progressPort.ItemCount);
			}
		}

		[TestMethod]
		public void SingleRequestResponse_Secure()
		{
			using (var space = new XcoAppSpace("tcp.port=0;tcp.key=secure123"))
			{
				var worker = space.Connect<SpaceMessage>("localhost:4508");
				var progressPort = new Port<double>();
				var responsePort = new Port<double>();
				var exceptionPort = space.Receive<Exception>(ex => Console.WriteLine("ERROR: " + ex));
				worker.PostWithCausality(new SpaceMessage { Num1 = 1, Num2 = 2, ProgressPort = progressPort, ResponsePort = responsePort }, exceptionPort);

				var response = responsePort.TestSync(3000);
				Assert.AreEqual(3, (int)response);
				Assert.AreEqual(20, progressPort.ItemCount);
			}
		}

		[TestMethod]
		[Tag("multi")]
		public void MultipleRequestResponse()
		{
			using (var space = new XcoAppSpace("tcp.port=0"))
			{
				var transportService = space.Info.TransportServices.First().Service as XcoTCPTransportService;

				var worker = space.Connect<SpaceMessage>("localhost:4505");
				var progressPort = new Port<double>();
				var responsePort = new Port<double>();

				//release all connections - the test should prove that if multiple messages are sent and a connection must be established, not responses are lost
				transportService.ReleaseConnections();
				Debug.WriteLine("Connections released");

				for (int i = 0; i < 10; i++)
				{
					worker.Post(new SpaceMessage { Num1 = 0, Num2 = i, ProgressPort = progressPort, ResponsePort = responsePort });
				}

				Thread.Sleep(7000);
				Assert.AreEqual(10, responsePort.ItemCount);
				Assert.AreEqual(20 * 10, progressPort.ItemCount);
			}
		}

		class TestResult
		{
			public TestResult()
			{
				Progress = new List<double>();
			}

			public string Address { get; set; }
			public double? Result { get; set; }
			public List<double> Progress { get; set; }

			public override string ToString()
			{
				return "TestResult[Addr=" + Address + ";Result=" + (Result != null ? Result.ToString() : "NULL") + ";ProgressCount=" + Progress.Count + "]";
			}
		}

		[TestMethod]
		[Tag("multi2")]
		public void MultipleRequestResponse2()
		{
			using (var space = new XcoAppSpace("tcp.port=0"))
			{
				var transportService = space.Info.TransportServices.First().Service as XcoTCPTransportService;

				var worker1 = space.ConnectWorker<Port<SpaceMessage>>("localhost:4505", "w1");
				var worker2 = space.ConnectWorker<Port<SpaceMessage>>("localhost:4506", "w2");
				var worker3 = space.ConnectWorker<Port<SpaceMessage>>("localhost:4507", "w3");
				
				var results = new List<TestResult>();

				//release all connections - the test should prove that if multiple messages are sent and a connection must be established, not responses are lost
				transportService.ReleaseConnections();
				var messagesReceived = 0;
				transportService.OnMessageReceived += (x, y, z) => Interlocked.Increment(ref messagesReceived);
				Debug.WriteLine("Connections released");

				for (int i = 0; i < 10; i++)
				{
					foreach (var w in new[] { worker1, worker2, worker3 })
					{
						var result  = new TestResult{Address = w.GetPortInfo().RemoteAddress};
						results.Add(result);
						var progressPort = space.Receive<double>(d => result.Progress.Add(d));
						var responsePort = space.Receive<double>(d => result.Result = d);
						var msg = new SpaceMessage { Num1 = 0, Num2 = i, ProgressPort = progressPort, ResponsePort = responsePort };
						w.Post(msg);
					}
				}

				Thread.Sleep(7000);
				Debug.WriteLine("Messages received: " + messagesReceived);
				Assert.AreEqual(3*10, results.Count(r => r.Result != null));
				foreach (var r in results)
				{
					Assert.AreEqual(20, r.Progress.Count);
				}
			}
		}

		//[TestMethod]
		//[Tag("multi3")]
		//public void MultipleRequestResponse3()
		//{
		//    for (int i = 0; i < 10; i++)
		//    {
		//        MultipleRequestResponse2();
		//    }
		//}

		[TestMethod]
		[Tag("largemsg")]
		public void LargeResponseMessage()
		{
			using (var space = new XcoAppSpace("tcp.port=0"))
			{
				var largeString = new StringBuilder(10000);
				for (int i = 0; i < 10000; i++)
				{
					largeString.Append('x');
				}
				var largeStr = largeString.ToString();

				var worker = space.Connect<LargeMessage>("localhost:4505");
				var responsePort = new Port<string>();
				worker.Post(new LargeMessage { Msg = largeStr, ResponsePort = responsePort });

				var response = responsePort.TestSync(3000);
				Assert.AreEqual(largeStr, response);
			}
		}

		[TestMethod]
		[Tag("response")]
		public void HighFrequencyResponse()
		{
			for (int i = 0; i < 3; i++)
			{
				using (var space = new XcoAppSpace("tcp.port=0"))
				{
					var transportService = space.Info.TransportServices.First().Service as XcoTCPTransportService;

					var worker = space.Connect<HighFrequencyResponseMsg>("localhost:4505");
					var responsePort = new Port<int>();
					worker.Post(new HighFrequencyResponseMsg { ResponsePort = responsePort });

					Thread.Sleep(7000);
					Assert.AreEqual(1000, responsePort.ItemCount);
				}
			}
		}

		[TestMethod]
		[Tag("errorhandling")]
		[ExpectedException(typeof(XcoException))]
		public void ConnectError_ThrownDuringConnectWorker()
		{
			using (var space1 = new XcoAppSpace("tcp.port=0"))
			{
				var w = space1.Connect<string>("localhost:4530"); //not existing address
			}
		}

		[TestMethod]
		[Tag("errorhandling")]
		public void ConnectError_PostedToExceptionPort()
		{
			using (var space1 = new XcoAppSpace("tcp.port=0"))
			{
				var w = CreateUncheckedWorkerProxy<Port<string>>(space1, "localhost:4530"); //not existing address

				var exceptionPort = new Port<Exception>();
				var ackPort = new Port<Ack>();
				w.PostWithCausality("test", exceptionPort, ackPort);

				var ex = exceptionPort.TestSync(5000);
				Assert.IsInstanceOfType(ex, typeof(XcoCommunicationException));
				Assert.AreEqual(0, ackPort.ItemCount);
			}
		}

		public T CreateUncheckedWorkerProxy<T>(IXcoAppSpace hostSpace, string remoteAddress) where T : IPort, new()
		{
			var connector = (Connector)hostSpace.Info.ServiceRegistry.Resolve<IConnector>();
			var workerInfo = connector.CreateUncheckedWorkerProxy<T>("test", remoteAddress, null);
			var wirer = hostSpace.Info.ServiceRegistry.Resolve<ICcrWirer>();
			var proxy = wirer.CreateWorkerProxy<T>(workerInfo);
			return proxy;
		}

		[TestMethod]
		[Tag("errorhandling")]
		public void SendSuccessful_PostedToAckPort()
		{
			using (var space1 = new XcoAppSpace("tcp.port=0"))
			{
				var w = space1.Connect<string>("localhost:4505");

				var exceptionPort = new Port<Exception>();
				var ackPort = new Port<Ack>();
				w.PostWithCausality("test", exceptionPort, ackPort);

				ackPort.TestSync(5000);
				Assert.AreEqual(0, exceptionPort.ItemCount);
			}
		}
	}
}